Здесь (или в сгенерированном вами html / pdf / ...) должны находиться картинки и слова к ним. Картинки должны содержать всевозможную интересную информацию, которые вы достали из данных с помощью методов визуализации. Слова должны являть собой ваши размышления об увиденном на картинке и вообще обо всем, что вы об этом думаете. Чем полезнее в совокупности будет представленная здесь информация и аккуратнее ваша работа, тем больше баллов вы за это получите.
вы можете в качестве research-хранилища прикрепить ноутбук / html / pdf / ... - что угодно, главное, чтобы все картинки и слова были видны. Если какие-то или все картинки не видны, их нужно прикрепить в классрум и указать название картинки, о которой идет речь в ваших рассуждениях
чтобы картинки были видны, можно:
fig.show(renderer='png') [нужно поставить kaleido: !pip install kaleido==0.1.0post1]чтобы проверить, видны ли картинки в колабе, можно просто сохранить ваш ноутбук и загрузить в колаб (bokeh вроде видно, но я не уверен, что он не исчезнет со временем - кидайте картинки на всякий случай или рисуйте в mpl / seaborn)
скидывать картинки нужно все в отдельном архиве
картинка "засчитывается" (за нее ставятся баллы), только если рядом с ней находятся какие-то слова, поясняющие, что вы на ней видите и на какие мысли это вас наталкивает (+ сама картинка видна или скинута, если скинута, то указано ее название)
распределения оригинальных признаков кидать не нужно!
Блок ниже дан, чтобы вы проверили, какие варианты видны в колабе, какие - нет. Его можно удалить, когда вам все станет ясно.
%load_ext autoreload
%autoreload 2
import pandas as pd
import numpy as np
from scipy.stats import norm
from functools import partial
import itertools
from utils.utils import *
from utils.reductors import *
from collections import namedtuple
import fast_hdbscan
import seaborn as sns
from seaborn import heatmap
import matplotlib.pyplot as plt
import phik
from phik import resources
from phik.binning import bin_data
from phik.report import plot_correlation_matrix
from sklearn import preprocessing
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
train = pd.read_csv('aim-2023-smoking/train.csv')
test = pd.read_csv('aim-2023-smoking/test.csv').assign(smoking=-1)
columns = train.columns
# Применим функцию проверки "чистоты" данных из второй домашки
get_df_info(train, thr=1)
| dtype | vc_max_el | vc_max_freq | nunique | nan | zero | empty_str | trash_score | |
|---|---|---|---|---|---|---|---|---|
| dental_caries | int64 | 0.797 | 0.797 | 2 | -1 | 0.797 | -1 | 0.797 |
| smoking | int64 | 0.564 | 0.564 | 2 | -1 | 0.564 | -1 | 0.564 |
| triglyceride | int64 | 0.011 | 0.011 | 349 | -1 | -1 | -1 | -1 |
| gtp | int64 | 0.036 | 0.036 | 214 | -1 | -1 | -1 | -1 |
| alt | int64 | 0.046 | 0.046 | 123 | -1 | -1 | -1 | -1 |
| ast | int64 | 0.068 | 0.068 | 81 | -1 | -1 | -1 | -1 |
| serum_creatinine | float64 | 0.222 | 0.222 | 21 | -1 | -1 | -1 | -1 |
| urine_protein | int64 | 0.948 | 0.948 | 5 | -1 | -1 | -1 | -1 |
| hemoglobin | float64 | 0.035 | 0.035 | 107 | -1 | -1 | -1 | -1 |
| ldl | int64 | 0.016 | 0.016 | 173 | -1 | -1 | -1 | -1 |
| hdl | int64 | 0.035 | 0.035 | 89 | -1 | -1 | -1 | -1 |
| age | int64 | 0.293 | 0.293 | 15 | -1 | -1 | -1 | -1 |
| height_cm | int64 | 0.237 | 0.237 | 11 | -1 | -1 | -1 | -1 |
| fasting_blood_sugar | int64 | 0.043 | 0.043 | 143 | -1 | -1 | -1 | -1 |
| relaxation | int64 | 0.133 | 0.133 | 61 | -1 | -1 | -1 | -1 |
| systolic | int64 | 0.076 | 0.076 | 88 | -1 | -1 | -1 | -1 |
| hearing_right | int64 | 0.976 | 0.976 | 2 | -1 | -1 | -1 | -1 |
| hearing_left | int64 | 0.977 | 0.977 | 2 | -1 | -1 | -1 | -1 |
| eyesight_right | float64 | 0.239 | 0.239 | 15 | -1 | -1 | -1 | -1 |
| eyesight_left | float64 | 0.240 | 0.240 | 16 | -1 | -1 | -1 | -1 |
| waist_cm | float64 | 0.042 | 0.042 | 380 | -1 | -1 | -1 | -1 |
| weight_kg | int64 | 0.156 | 0.156 | 19 | -1 | -1 | -1 | -1 |
| cholesterol | int64 | 0.015 | 0.015 | 180 | -1 | -1 | -1 | -1 |
Как видно, подаваемые данные довольно чисты, нет пропусков.
# Посмтроим как распределены данные по курильщикам
train['smoking'].value_counts() / len(train)
0 0.5636 1 0.4364 Name: smoking, dtype: float64
Мы имеем примерно равное распределение курильщиков и не курильщиков в трейне. Это хорошо, т.к мы можем использовать методы, которые работают со сбалансированными классами. В дальнейшем нам придётся выяснить является ли тест так же сбалансированным
В задании попросили не кидать распределение оригинальных признаков, значит я не буду этого делать
Но если мы построим распределение признаков, можно уже выделить какие-то признаки, распределение которых отличается у курильщиков и не курильщиков. Я бы выделил такие признаки как: heigh_cm (любопытная особенность), weight_kg, hemoglobin (очень хорошо выделяется) и немного других. В ходе исследования попробуем выявить те, которые нам могут понадобиться.
Для определения закономерностей давайте воспользуемся библиотекой phik, которая умеет искать нелинейную корреляцию между параметрами. По сути это умная корреляция. Вот ссылка на исследование - link (Надеюсь за использование этой библиотеки не забанят, т.к это просто статистический метод анализа)
# Для хорошей работы phik, ему лучше указать все интервальные переменные
data_types = {'age': 'interval', #5-years gap
'height_cm':'interval',
'weight_kg':'ordinal',
'waist_cm':'interval', #Длина окружности талии
'eyesight_left':'ordinal',
'eyesight_right':'ordinal',
'hearing_left' :'categorical',
'hearing_right' : 'categorical',
'systolic' : 'interval',#Кровяное давление
'relaxation' : 'interval',#Кровяное давление
'fasting_blood_sugar' : 'interval',
'cholesterol' : 'interval',
'triglyceride' : 'interval',
'hdl': 'interval', #тип холестерина
'ldl' : 'interval', #тип холестерина
'hemoglobin': 'interval',
'urine_protein': 'categorical',
'serum_creatinine':'ordinal',
'ast': 'interval',#тип глутаминоксалоуксусной трансаминазы
'alt':'interval', #тип глутаминоксалоуксусной трансаминазы
'gtp':'interval',
'dental_caries': 'categorical',
'smoking': 'categorical',
}
interval_cols = [col for col, v in data_types.items() if v=='interval' and col in train.columns]
phik_matrix = phik.phik_matrix(train, interval_cols=interval_cols)
heatmap(phik_matrix)
<Axes: >
Как и было сказано, у нас хорошо выделяются признаки, которые были сказаны выше и несколько других. Давайте выведем их топ
phik_matrix['smoking'].sort_values(ascending=False).to_frame().head(10)
| smoking | |
|---|---|
| smoking | 1.000000 |
| height_cm | 0.620378 |
| hemoglobin | 0.593640 |
| triglyceride | 0.437533 |
| weight_kg | 0.417608 |
| serum_creatinine | 0.369918 |
| waist_cm | 0.347437 |
| hdl | 0.337730 |
| age | 0.277979 |
| gtp | 0.275340 |
Теперь имея на руках корреляции и распределения, мы можем попробовать выделить признаки, которые могут нам понадобиться. Так же попробуем преобразовать некоторые признаки, чтобы лучше разделять курильщиков и некурильщиков.
# Выделим признаки, с которыми будем работать
favorite = [
'height_cm',
'hemoglobin',
'triglyceride',
'weight_kg',
'serum_creatinine',
'waist_cm',
'hdl',
'age',
'gtp'
]
# будем хранить в одельной таблице наши преобразования
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
def proc(data_orig, inplace=False, scale_before=False, scale_after=True):
'''
Обработка признаков, для лучшего разделения данных. Принимает DataFrame с данными курильщиков и возвращает обработанную таблицу
inplace - bool, изменяем ли мы изначальную таблицу или же её копию
scale_before - bool, отвечает за то, будем ли мы стандартизировать данные до их обработки
scale_after - bool, отвечает за то, будем ли мы стандартизировать данные до их обработки
После того как что-то добавить или исправить,
желательно предварительно посмотреть на график до и после, воспользовшись compose_feature
тем самым мы лучше будем понимать как у нас получилось преобразовать фитчу
'''
if inplace == False:
data = data_orig.copy()
else:
data = data_orig
if scale_before==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
data['height_cm'] = np.power(data.height_cm, 1)
data['waist_cm'] = (data['waist_cm'] / 10).round() * 10 # округляем значения признака, чтобы лучше билось на бины
data['hemoglobin'] = np.clip(data.hemoglobin, 9, 25)
data['serum_creatinine'] = np.clip(data['serum_creatinine'], 0, 2) # у этого признака есть небольшое количество выбросов, поэтому давайте их "обрежем"
data['serum_creatinine'] = np.log1p(data.serum_creatinine)
data['gtp'] = np.log1p(data.gtp)
data['weight_kg'] = np.power(data.weight_kg, 2)
data['triglyceride'] = np.log1p(data.triglyceride)
data['age'] = np.power(data.age, 0.5)
if scale_after==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
return data
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'height_cm')
fig.show(renderer='png')
Пока не ясно но почему-то тут появился провал около нуля.
# Тут на самом деле я бы убрал такое распределение, т.к. оно стало менее плавным
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'hemoglobin')
fig.show(renderer='png')
# Это преобразование выглядит не очень, т.к если сложить эти колонки, то может получитсять не очень хорошее распределение.
# Вообще сам по себе признак по взгляду будет только мешать для выявления кластеров, но мы это проверим
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'triglyceride')
fig.show(renderer='png')
#распределение признака после преобразования
proc(train_proc)['triglyceride'].hist(bins=50)
<Axes: >
# распределение признака истинное. Как мне кажется, что тут более выражены отдельные моды, что кажется лучше будет для отделения
train['triglyceride'].hist(bins=50)
<Axes: >
# особо сильно возведение в степерь возраста не даёт преимуществ.
# И как я наблюдал дальше, возведение только возведение в степень не даёт сильного отличия
# (важное наблюдение, дальше я попробую как-то модифицировать эту идею)
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'weight_kg')
fig.show(renderer='png')
# а вот для этого признака распределение стало более симпотичным
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'gtp')
fig.show(renderer='png')
# Взятие корня тоже не сильно даёт результата
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'age')
fig.show(renderer='png')
# Это изменение отменить
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'hdl')
fig.show(renderer='png')
# Произошло округление и распределение стало более симпотичным. Как мне кажется, что так можно попробовать оставить
fig = compose_feature(train_proc, proc(train_proc, scale_before=False, scale_after=True), 'waist_cm')
fig.show(renderer='png')
Итак, перед тем как, попробуем построить кластеры, с которыми будем рассматривать различимость данных, мы посмотрим как будут строиться кластеры на сырых данных:
plot_data_train = train.copy()
plot_data_test = test.sample(1_000, random_state=911)
plot_data = pd.concat([plot_data_train, plot_data_test], axis=0, sort=False, ignore_index=True)
sample = plot_data
# Делаем заранее расчёты, которые нужны будут для tsne
preset_init = openTSNE.initialization.pca(sample, random_state=911)
preset_affinities_cos = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='cosine',
method='hnsw',
n_jobs=1,
random_state=911)
preset_affinities_l2 = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='l2',
method='hnsw',
n_jobs=1,
random_state=911)
make_tsne_preset_cos = partial(make_tsne, init=preset_init, affinities=preset_affinities_cos)
make_tsne_preset_l2 = partial(make_tsne, init=preset_init, affinities=preset_affinities_l2)
hue_info = namedtuple('hue_info', ['field_name', 'is_categorical'])
def plot_standard_embeddings(plot_data, features, return_results=False):
'''
рисует "какие-то" эмбеддинги с hue='smoking' на одних и тех же фичах features
'''
mapper_dict = {
'tsne perplex=30 exagger=4 dof=0.5 metric=l2': {
'params': {
'n_jobs': 1,
'perplexity': 30,
'verbose': False,
'n_components': 2,
'early_exaggeration_iter': 300,
'early_exaggeration': 24,
'n_iter': 1000,
'exaggeration': 4,
'metric': 'l2',
'dof': 0.5,
'neighbors': 'pynndescent'
},
'func': make_tsne_preset_l2,
},
'tsne perplex=30 exagger=4 dof=0.5 metric=cosine': {
'params': {
'n_jobs': 1,
'perplexity': 30,
'verbose': False,
'n_components': 2,
'exaggeration': 4,
'metric': 'cosine',
'early_exaggeration_iter': 300,
'early_exaggeration': 24,
'n_iter': 1000,
'dof': 0.5,
'neighbors': 'pynndescent'
},
'func': make_tsne_preset_cos,
},
'UMAP 2D n_neighbors=100 metric=l2': {
'params': {
'n_neighbors': 100,
'min_dist': 0.1,
'metric': 'l2',
'n_jobs': 1,
'verbose': False,
'n_components': 2
},
'func': make_umap,
},
'UMAP 2D n_neighbors=100 metric=cosine': {
'params': {
'n_neighbors': 100,
'min_dist': 0.1,
'metric': 'cosine',
'n_jobs': 1,
'verbose': False,
'n_components': 2
},
'func': make_umap,
}
}
res = plot_dim_reduction(
data=plot_data,
mapper_dict=mapper_dict,
default_features=features,
default_hue_info=hue_info(field_name='smoking', is_categorical=True),
row_height=550,
return_results=return_results
)
return res
res_base = plot_standard_embeddings(sample, favorite, return_results=True)
Вот такая каша получается, если мы возьмём за основу сырые признаки без обработки (правый нижний кластер похож на кита, наверное это какая-то хитрая закономерность). Давайте посмотрим как будет вести себя наша первая обработка.
# Cохраним изменения обработки
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
proc(train_proc, scale_before=False, scale_after=True, inplace=True)
proc(test_proc, scale_before=False, scale_after=True, inplace=True)
plot_data_train = train_proc.copy()
plot_data_test = test_proc.sample(1_000, random_state=911)
plot_data = pd.concat([plot_data_train, plot_data_test], axis=0, sort=False, ignore_index=True)
sample = plot_data
# Делаем заранее расчёты, которые нужны будут для tsne
preset_init = openTSNE.initialization.pca(sample, random_state=911)
preset_affinities_cos = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='cosine',
method='hnsw',
n_jobs=1,
random_state=911)
preset_affinities_l2 = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='l2',
method='hnsw',
n_jobs=1,
random_state=911)
make_tsne_preset_cos = partial(make_tsne, init=preset_init, affinities=preset_affinities_cos)
make_tsne_preset_l2 = partial(make_tsne, init=preset_init, affinities=preset_affinities_l2)
res_1 = plot_standard_embeddings(sample, favorite, return_results=True)
Как видим tsne не смог переварить эту обработку, но можно заметить, что UMAP смог отделить кластер с некурящими людьми (с ошибками, но всё же), но ему не удалось отделить курящих от некурящих (а это непростая задача)
В этой предобработке я уберу лишние, на мой взгляд на гистрограммы, какие-то обработки данных и посмотрим, что получится
favorite = [
'height_cm',
'hemoglobin',
'triglyceride',
'weight_kg',
'serum_creatinine',
'waist_cm',
'hdl',
'age',
'gtp'
]
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
# отличается от первой версии тем, что мы меняем только
def proc_v2(data_orig, inplace=False, scale_before=False, scale_after=True):
if inplace == False:
data = data_orig.copy()
else:
data = data_orig
if scale_before==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
data['waist_cm'] = (data['waist_cm'] / 10).round() * 10 # округляем значения признака, чтобы лучше билось на бины
data['serum_creatinine'] = np.clip(data['serum_creatinine'], 0, 2) # у этого признака есть небольшое количество выбросов, поэтому давайте их "обрежем"
data['serum_creatinine'] = np.log1p(data.serum_creatinine)
data['gtp'] = np.log1p(data.gtp)
if scale_after==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
return data
proc_v2(train_proc, scale_before=False, scale_after=True, inplace=True)
proc_v2(test_proc, scale_before=False, scale_after=True, inplace=True)
print()
Теперь давайте попробуем кластеризовать наши данные и посмотрим, что из этого может выйти
plot_data_train = train_proc.copy()
plot_data_test = test_proc.sample(1_000, random_state=911)
plot_data = pd.concat([plot_data_train, plot_data_test], axis=0, sort=False, ignore_index=True)
sample = plot_data
preset_init = openTSNE.initialization.pca(sample, random_state=911)
preset_affinities_cos = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='cosine',
method='hnsw',
n_jobs=1,
random_state=911)
preset_affinities_l2 = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='l2',
method='hnsw',
n_jobs=1,
random_state=911)
make_tsne_preset_cos = partial(make_tsne, init=preset_init, affinities=preset_affinities_cos)
make_tsne_preset_l2 = partial(make_tsne, init=preset_init, affinities=preset_affinities_l2)
res_2 = plot_standard_embeddings(sample, favorite, return_results=True)
На самом деле что на обработке 1 и обработке 2 очень похожая картина, видимо нам нужен какой-то инструмент, который будет помогать выбирать преобразования признаков, чтобы выходило лучше
# Давайте посмотрим как у нас изменилось зависимость признаков от smoking до и после предобработки
def phik_corr_comp(before, after):
phik_mat_before = phik.phik_matrix(before, interval_cols=interval_cols)['smoking'].to_frame()
phik_mat_after = phik.phik_matrix(after, interval_cols=interval_cols)['smoking'].to_frame()
phik_mat_before = phik_mat_before.round(decimals=2)
phik_mat_after = phik_mat_after.round(decimals=2)
phik_comp = pd.concat([phik_mat_before, phik_mat_after], axis=1, keys=['phik_corr_before', 'phik_corr_after'])
phik_comp = phik_comp.droplevel(level=1, axis=1)
phik_comp = phik_comp.sort_values('phik_corr_after', ascending=False)
phik_comp['result'] = phik_comp.apply(lambda row: 'Good' if row['phik_corr_after'] > row['phik_corr_before'] else (
'=' if row['phik_corr_after'] == row['phik_corr_before'] else 'Bad'), axis=1)
return phik_comp.head(len(favorite)+1)
phik_corr_comp(train, train_proc)
| phik_corr_before | phik_corr_after | result | |
|---|---|---|---|
| smoking | 1.00 | 1.00 | = |
| height_cm | 0.62 | 0.62 | = |
| hemoglobin | 0.59 | 0.59 | = |
| gtp | 0.28 | 0.54 | Good |
| triglyceride | 0.44 | 0.44 | = |
| weight_kg | 0.42 | 0.42 | = |
| serum_creatinine | 0.37 | 0.41 | Good |
| hdl | 0.34 | 0.34 | = |
| age | 0.28 | 0.28 | = |
| waist_cm | 0.35 | 0.24 | Bad |
Отлично! Теперь у нас появился новый инструмент для анализа того, как какое изменение признака лучше сказывается на корреляции того, является ли признак важен для определения курильщиков и не курильщиков. Как видно, логарифмирование gtp даёт хороший прирост, а округление waist_cm хуже сказывается для определения зависимости. Помимо этого давайте ещё уберём изменения, которые практически не изменили корреляцию.
# Сравним proc() и proc_v2()
train_proc1 = train[[*favorite, 'smoking']].copy()
train_proc2 = train[[*favorite, 'smoking']].copy()
proc(train_proc1, inplace=True)
proc_v2(train_proc2, inplace=True)
phik_corr_comp(train_proc1, train_proc2)
| phik_corr_before | phik_corr_after | result | |
|---|---|---|---|
| smoking | 1.00 | 1.00 | = |
| height_cm | 0.62 | 0.62 | = |
| hemoglobin | 0.60 | 0.59 | Bad |
| gtp | 0.54 | 0.54 | = |
| triglyceride | 0.44 | 0.44 | = |
| weight_kg | 0.42 | 0.42 | = |
| serum_creatinine | 0.41 | 0.41 | = |
| hdl | 0.34 | 0.34 | = |
| age | 0.25 | 0.28 | Good |
| waist_cm | 0.24 | 0.24 | = |
Так, мы смогли сделать улучшения с точки зрения сырых данных, но между обработками у нас не так много изменений, что не есть хорошо!
После изменения признаков, давайте попробуем добавить новый признак. Для этого можно попробовать их складывать, умножать, делить между собой и посмотреть, что по итогу получится
favorite = [
'height_cm',
'hemoglobin',
'triglyceride',
'weight_kg',
'serum_creatinine',
'waist_cm',
'hdl',
'age',
'gtp'
]
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
def proc_v3_1(data_orig, inplace=False, scale_before=False, scale_after=True):
if inplace == False:
data = data_orig.copy()
else:
data = data_orig
if scale_before==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
data['serum_creatinine'] = np.clip(data['serum_creatinine'], 0, 2) # у этого признака есть небольшое количество выбросов,
# поэтому давайте их "обрежем"
data['serum_creatinine'] = np.log1p(data.serum_creatinine)
data['gtp'] = np.log1p(data.gtp)
# Добавляю новый признак.
# Я руководстуюсь такой логикой: давайте возьмём самые влиятельные признаки и попробуем сделать их композицию
data['hhg'] = (
np.power(data['height_cm'], 1) * np.power(data['hemoglobin'], 1) * np.power(data['gtp'], 1)
)
if 'hhg' not in favorite:
favorite.append('hhg')
if scale_after==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
return data
proc_v3_1(train_proc, inplace=True)
proc_v3_1(test_proc, inplace=True)
print()
Теперь давайте посмотрим хорошее ли получилось распределение у признака с height_cm (лучший разделитель):
# Отрисует как распределён новый признак
fig = make_subplots(rows=1, cols=2, subplot_titles=['height_cm', 'hhg'])
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==0]['height_cm'],
name='no_smoke',
marker_color='#08D9D6',
histnorm='probability'),
row=1, col=1)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==1]['height_cm'],
name='smoke',
marker_color='#E32636',
histnorm='probability'),
row=1, col=1)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==0]['hhg'],
name='no_smoke',
marker_color='#08D9D6',
histnorm='probability'),
row=1, col=2)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==1]['hhg'],
name='smoke',
marker_color='#E32636',
histnorm='probability'),
row=1, col=2)
fig.update_layout(height=400, title_text='Сравним распределение нового признака с распределением height_cm', showlegend=False)
fig.show(renderer='png')
Я очень доволен получившимся распределением. По нему видно, что у нас явно стало меньше пересечений. Давайте посмотрим, что скажет нам коэффициент корреляции
# т.к мы начали добавлять в favorite признаки, которых нет в trian, давайте напишем мини функцию, которая делает favorite стандартным
def set_fav():
favorite = [
'height_cm', 'hemoglobin', 'triglyceride', 'weight_kg', 'serum_creatinine', 'waist_cm', 'hdl', 'age', 'gtp'
]
return favorite
favorite = set_fav()
interval_cols.append('hhg')
train_proc1 = train[[*favorite, 'smoking']].copy()
train_proc2 = train[[*favorite, 'smoking']].copy()
proc_v2(train_proc1, inplace=True)
proc_v3_1(train_proc2, inplace=True)
phik_corr_comp(train_proc1, train_proc2)
| phik_corr_before | phik_corr_after | result | |
|---|---|---|---|
| smoking | 1.00 | 1.00 | = |
| hhg | NaN | 0.68 | Bad |
| height_cm | 0.62 | 0.62 | = |
| hemoglobin | 0.59 | 0.59 | = |
| gtp | 0.54 | 0.54 | = |
| triglyceride | 0.44 | 0.44 | = |
| weight_kg | 0.42 | 0.42 | = |
| serum_creatinine | 0.41 | 0.41 | = |
| waist_cm | 0.24 | 0.35 | Good |
| hdl | 0.34 | 0.34 | = |
| age | 0.28 | 0.28 | = |
Отлично! Мы смогли создать признак, который даёт хорошую корреляцию с курильщиками (не обращайте внимание на result Bad, он не может обработать сравнение с NaN). И помимо этого ещё улучшили другой проказатель.
Но давайте ещё попробуем поиграться с этим признаком. Что будет, если мы попробуем возводить некоторые множители в степень?
favorite = set_fav()
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
def proc_v3_2(data_orig, inplace=False, scale_before=False, scale_after=True):
if inplace == False:
data = data_orig.copy()
else:
data = data_orig
if scale_before==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
data['serum_creatinine'] = np.clip(data['serum_creatinine'], 0, 2) # у этого признака есть небольшое количество выбросов,
# поэтому давайте их "обрежем"
data['serum_creatinine'] = np.log1p(data.serum_creatinine)
data['gtp'] = np.log1p(data.gtp)
# Добавляю новый признак.
# Я руководстуюсь такой логикой: давайте возьмём самые влиятельные признаки и попробуем сделать их композицию
# Добавляю в этой версии степени
data['hhg'] = (
np.power(data['height_cm'], 3) * np.power(data['hemoglobin'], 2) * np.power(data['gtp'], 1)
)
if 'hhg' not in favorite:
favorite.append('hhg')
if scale_after==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
return data
proc_v3_2(train_proc, inplace=True)
proc_v3_2(test_proc, inplace=True)
print()
# Отрисует как распределён новый признак
fig = make_subplots(rows=1, cols=2, subplot_titles=['height_cm', 'hhg'])
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==0]['height_cm'],
name='no_smoke',
marker_color='#08D9D6',
histnorm='probability'),
row=1, col=1)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==1]['height_cm'],
name='smoke',
marker_color='#E32636',
histnorm='probability'),
row=1, col=1)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==0]['hhg'],
name='no_smoke',
marker_color='#08D9D6',
histnorm='probability'),
row=1, col=2)
fig.add_trace(go.Histogram(x=train_proc[train_proc['smoking']==1]['hhg'],
name='smoke',
marker_color='#E32636',
histnorm='probability'),
row=1, col=2)
fig.update_layout(height=400, title_text='Сравним распределение нового признака с распределением height_cm', showlegend=False)
fig.show(renderer='png')
Так, картинка изменилась (в отличии от случаев, когда мы возводили в степень один признак -- инсайт!). Давайте посмотрим, как это скажется на картине корреляции
favorite = set_fav()
train_proc1 = train[[*favorite, 'smoking']].copy()
train_proc2 = train[[*favorite, 'smoking']].copy()
proc_v3_1(train_proc1, inplace=True)
proc_v3_2(train_proc2, inplace=True)
phik_corr_comp(train_proc1, train_proc2)
| phik_corr_before | phik_corr_after | result | |
|---|---|---|---|
| smoking | 1.00 | 1.00 | = |
| hhg | 0.68 | 0.70 | Good |
| height_cm | 0.62 | 0.62 | = |
| hemoglobin | 0.59 | 0.59 | = |
| gtp | 0.54 | 0.54 | = |
| triglyceride | 0.44 | 0.44 | = |
| weight_kg | 0.42 | 0.42 | = |
| serum_creatinine | 0.41 | 0.41 | = |
| waist_cm | 0.35 | 0.35 | = |
| hdl | 0.34 | 0.34 | = |
| age | 0.28 | 0.28 | = |
Немного, но мы смогли улучшить этот признак! Давайте теперь попробуем посмотреть какие класстеры у нас получаются.
plot_data_train = train_proc.copy()
plot_data_test = test_proc.sample(1_000, random_state=911)
plot_data = pd.concat([plot_data_train, plot_data_test], axis=0, sort=False, ignore_index=True)
sample = plot_data
preset_init = openTSNE.initialization.pca(sample, random_state=911)
preset_affinities_cos = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='cosine',
method='hnsw',
n_jobs=1,
random_state=911)
preset_affinities_l2 = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='l2',
method='hnsw',
n_jobs=1,
random_state=911)
make_tsne_preset_cos = partial(make_tsne, init=preset_init, affinities=preset_affinities_cos)
make_tsne_preset_l2 = partial(make_tsne, init=preset_init, affinities=preset_affinities_l2)
res_3 = plot_standard_embeddings(sample, favorite, return_results=True)
Вот это уже будет лучше, чем на прошлых обработках. Но видно, что ещё есть куда расти. Под розовыми точками находятся жёлтые и кластер некурящих имеет какое-то количество ошибок.
На этом этапе я попробую посмотреть, что будет, если мы уберём слабые признаки. Может ли это в нашем случае улучшить ситуацию?
# Легко увидеть, какие именно пункты я решил убрать.
# Если это не поможет, я верну их обратно и буду пробовать создавать новые признаки
favorite = [
'height_cm',
'hemoglobin',
'triglyceride',
'weight_kg',
'serum_creatinine',
#'waist_cm',
#'hdl',
#'age',
'gtp'
]
train_proc = train[[*favorite, 'smoking']].copy()
test_proc = test[[*favorite, 'smoking']].copy()
def proc_v4(data_orig, inplace=False, scale_before=False, scale_after=True):
if inplace == False:
data = data_orig.copy()
else:
data = data_orig
if scale_before==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
data['serum_creatinine'] = np.clip(data['serum_creatinine'], 0, 2) # у этого признака есть небольшое количество выбросов,
# поэтому давайте их "обрежем"
data['serum_creatinine'] = np.log1p(data.serum_creatinine)
data['gtp'] = np.log1p(data.gtp)
# Добавляю новый признак.
# Я руководстуюсь такой логикой: давайте возьмём самые влиятельные признаки и попробуем сделать их композицию
# Добавляю в этой версии степени
data['hhg'] = (
np.power(data['height_cm'], 3) * np.power(data['hemoglobin'], 2) * np.power(data['gtp'], 1)
)
if 'hhg' not in favorite:
favorite.append('hhg')
if scale_after==True:
scaler = preprocessing.StandardScaler().fit(data.drop('smoking', axis=1))
data[favorite] = scaler.transform(data.drop('smoking', axis=1))
return data
proc_v4(train_proc, inplace=True)
proc_v4(test_proc, inplace=True)
print()
plot_data_train = train_proc.copy()
plot_data_test = test_proc.sample(1_000, random_state=911)
plot_data = pd.concat([plot_data_train, plot_data_test], axis=0, sort=False, ignore_index=True)
sample = plot_data
preset_init = openTSNE.initialization.pca(sample, random_state=911)
preset_affinities_cos = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='cosine',
method='hnsw',
n_jobs=1,
random_state=911)
preset_affinities_l2 = openTSNE.affinity.PerplexityBasedNN(sample.drop('smoking', axis=1),
perplexity=30,
metric='l2',
method='hnsw',
n_jobs=1,
random_state=911)
make_tsne_preset_cos = partial(make_tsne, init=preset_init, affinities=preset_affinities_cos)
make_tsne_preset_l2 = partial(make_tsne, init=preset_init, affinities=preset_affinities_l2)
res_4 = plot_standard_embeddings(sample, favorite, return_results=True)
Хм, картинка не стала сильно лучше... Видимо нужно подобрать новый признак